package org.xuk.spring.ws.config;

import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.ApplicationListener;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.messaging.simp.broker.BrokerAvailabilityEvent;
import org.springframework.messaging.simp.config.ChannelRegistration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.scheduling.concurrent.DefaultManagedTaskScheduler;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketTransportRegistration;
import org.springframework.web.socket.server.HandshakeInterceptor;
import org.xuk.spring.ws.constants.WsConstants;
import org.xuk.spring.ws.inteceptor.WebSocketInboundInterceptor;

import java.util.Map;

/**
 * WebSocket 配置
 * <p>
 * EnableWebSocketMessageBroker 注解开启使用 STOMP 协议来传输基于代理的消息，使 Controller 支持 MessageMapping 注解。
 *
 * @author xuker
 */
@Slf4j
@Setter
@Configuration
@EnableWebSocketMessageBroker
@EnableConfigurationProperties({UserSessionProperties.class})
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer, ApplicationListener<BrokerAvailabilityEvent> {
    private final WebSocketInboundInterceptor webSocketInboundInterceptor;

    public WebSocketConfig(WebSocketInboundInterceptor webSocketInboundInterceptor) {
        this.webSocketInboundInterceptor = webSocketInboundInterceptor;
    }

    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        // 继承DefaultHandshakeHandler并重写determineUser方法，可以自定义如何确定用户
        // 添加方法：registry.addEndpoint("/ws").setHandshakeHandler(handshakeHandler)
        registry.addEndpoint("/ws")
                .setAllowedOriginPatterns("*").addInterceptors(new HandshakeInterceptor() {
                    @Override
                    public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map<String, Object> attributes) throws Exception {
                        log.info("websocket handshake before url:{}", request.getURI());
                        return true;
                    }

                    @Override
                    public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Exception exception) {
                        log.info("websocket handshake connect success");
                    }
                })
                .withSockJS();
    }

    @Override
    public void configureWebSocketTransport(WebSocketTransportRegistration registry) {
        registry.setMessageSizeLimit(10240).setSendBufferSizeLimit(10240).setSendTimeLimit(3000);
    }

    @Override
    public void configureClientInboundChannel(ChannelRegistration registration) {
        registration.interceptors(webSocketInboundInterceptor);
        registration.taskExecutor().corePoolSize(5)
                .maxPoolSize(5)
                .queueCapacity(1024)
                .keepAliveSeconds(60);
    }

    /**
     * 配置消息代理
     */
    @Override
    public void configureMessageBroker(MessageBrokerRegistry registry) {
        /**
         * 这里表示前端往/app路径推送， 如果后端定义一个controller ->@MessageMapping("/echo"),
         * stompClient.send("/app/echo",{},...)
         * 这时，消息会被推送到注解对应的@MessageMapping("/echo")方法上
         */
        registry.setApplicationDestinationPrefixes("/app");
        /**
         * stompClient.subscribe("/user/topic/subNewMsg",...)
         * 这个时候，后端推送消息应该这么写
         * msgOperations.convertAndSendToUser(username, "/topic/subNewMsg", msg);
         * 即去掉了/user前缀
         */
        registry.setUserDestinationPrefix(WsConstants.USER_DESTINATION_PREFIX);
        /**
         * broker中的前缀怎么解释？
         * 可以理解为广播消息的前缀
         * 如果 stompClient.subscribe("/topic/hello",...)
         * 后端这么发送:msgOperations.convertAndSend("/topic/hello","hello world!!!");
         * 那么所有的客户端凡是订阅了该topic的，都会收到此广播的消息
         */
        registry.enableSimpleBroker("/topic", "/queue")
                //配置stomp协议里, server返回的心跳
                .setHeartbeatValue(new long[]{10000L, 10000L})
                //配置发送心跳的scheduler
                .setTaskScheduler(new DefaultManagedTaskScheduler());

        registry.setPreservePublishOrder(true);
    }

    @Override
    public void onApplicationEvent(BrokerAvailabilityEvent event) {
        if (!event.isBrokerAvailable()) {
            log.warn("stomp broker is not available!!!!!!!!");
        } else {
            log.info("stomp broker is available");
        }
    }
}
